﻿using System;
using System.Drawing;

namespace Pibby.Controls
{
	public struct HslColor
	{
		#region Attributes

		private double _alpha; // 0 -> 1
		private double _hue; // 0 -> 360
		private double _saturation; // 0 -> 1
		private double _luminosity; // 0 -> 1

		private const int MaxAngleValue = 360;
		private const int MinAngleValue = 0;
		private const int MaxColorValue = 255;
		private const int MinColorValue = 0;

		#endregion Attributes

		#region Constructors

		public HslColor(double hue, double saturation, double luminosity)
			: this (1.0, hue, saturation, luminosity)
		{
		}

		public HslColor(double alpha, double hue, double saturation, double luminosity)
		{
			this._alpha = alpha;
			this._hue = hue;
			this._saturation = saturation;
			this._luminosity = luminosity;
		}

		public HslColor(int red, int green, int blue)
			: this(Color.FromArgb(red, green, blue))
		{
		}

		public HslColor(Color color)
		{
			this._alpha = ByteToPercentage (color.A);
			this._hue = color.GetHue();
			this._luminosity = color.GetBrightness();
			this._saturation = color.GetSaturation();
		}

		#endregion Constructors

		#region ToString

		public override string ToString()
		{
			return String.Format(System.Globalization.CultureInfo.InvariantCulture,
									"{0:#0.##} {1:#0.##} {2:#0.##}",
									this.Hue,
									this.Saturation,
									this.Luminosity);
		}

		#endregion ToString

		#region Properties

		public double Alpha
		{
			get { return _alpha; }
			set { _alpha = NormalizePercentage (value); }
		}

		public double Hue
		{
			get { return _hue; }
			set { _hue = NormalizeDegree(value); }
		}

		public double Saturation
		{
			get { return _saturation; }
			set { _saturation = NormalizePercentage (value); }
		}

		public double Luminosity
		{
			get { return _luminosity; }
			set { _luminosity = NormalizePercentage (value); }
		}

		#endregion Properties

		#region Canned Colours

		public static HslColor Red 
		{ 
			get
			{
				return new HslColor(0.0, 100.0, 0.5);
			} 
		}

		public static HslColor Green
		{
			get
			{
				return new HslColor(120.0, 100.0, 0.5);
			}
		}

		public static HslColor Blue
		{
			get
			{
				return new HslColor(240.0, 100.0, 0.5);
			}
		}

		#endregion Canned Colours

		#region Private Methods

		private static double ByteToPercentage(byte v)
		{
			return ((double)v) / MaxColorValue;
		}

		private static byte PercentageToByte(double percent)
		{
			percent *= MaxColorValue;
			percent += .5;

			if (percent > MaxColorValue)
				percent = MaxColorValue;

			if (percent < MinColorValue)
				percent = MinColorValue;

			return (byte)percent;
		}

		private static double NormalizePercentage(double d)
		{
			if (d < 0)
				d += 1;

			if (d > 1)
				d -= 1;

			return d;
		}

		private static double NormalizeDegree(double d)
		{
			if (d < MinAngleValue)
				d += MaxAngleValue;

			if (d > MaxAngleValue)
				d -= MaxAngleValue;

			return d;
		}

		private double getComponent(double tc, double p, double q)
		{
			double componentValue = 0.0;

			if (tc < (1.0 / 6.0))
				componentValue = p + ((q - p) * 6 * tc);
			else if (tc < .5)
				componentValue = q;
			else if (tc < (2.0 / 3.0))
				componentValue = p + ((q - p) * 6 * ((2.0 / 3.0) - tc));
			else
				componentValue = p;

			return componentValue;
		}

		#endregion Private Methods

		public HslColor Lighten(double byPercent)
		{
			return new HslColor(this._alpha, 
								this._hue, 
								this._saturation,
								NormalizePercentage (this._luminosity + byPercent));
		}

		public HslColor Darken(double byPercent)
		{
			return Lighten(-byPercent);
		}

		public Color ToColor()
		{
			double q = 0;

			if (this._luminosity < 0.5)
			{
				q = this._luminosity * (1 + this._saturation);
			}
			else
			{
				q = this._luminosity + this._saturation - (this._luminosity * this._saturation);
			}

			double p = (2 * this._luminosity) - q;
			double hk = this._hue / 360;
			double red = getComponent(NormalizePercentage(hk + (1.0 / 3.0)), p, q);
			double green = getComponent(NormalizePercentage(hk), p, q);
			double blue = getComponent(NormalizePercentage(hk - (1.0 / 3.0)), p, q);

			return Color.FromArgb(PercentageToByte(this._alpha), 
									PercentageToByte(red), 
									PercentageToByte(green), 
									PercentageToByte(blue));
		}
	}

}


